package controllers

import java.io.File

import actor.{MissionManageActor, StepMissionManageActor}
import akka.actor.{Actor, ActorSystem, PoisonPill, Props}
import akka.stream.Materializer
import command.{CommandUtils, MissionUtils}
import dao._
import javax.inject.Inject
import models.Tables._
import org.apache.commons.io.FileUtils
import org.joda.time.DateTime
import play.api.libs.json.{JsValue, Json}
import play.api.libs.streams.ActorFlow
import play.api.mvc.{AbstractController, ControllerComponents, WebSocket}
import shared.VarTool
import tool.Pojo.{MyDao, SampleInfoFileCheckData}
import tool.{FileTool, FormTool, Tool, WebTool}
import utils.Utils

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
import scala.language.postfixOps

/**
 * Created by yz on 22/6/2020
 */
class StepMissionController @Inject()(cc: ControllerComponents)(
  implicit val system: ActorSystem,
  implicit val materializer: Materializer,
  implicit val missionDao: MissionDao,
  implicit val sampleDao: SampleDao,
  implicit val configDao: ConfigDao,
  implicit val stepMissionDao: StepMissionDao,
  implicit val modeDao: ModeDao
) extends AbstractController(cc) {

  implicit val dao = MyDao(missionDao, sampleDao, configDao, stepMissionDao)

  val missionManageActor = system.actorOf(
    Props(new StepMissionManageActor())
  )
  missionManageActor ! "ask"


  def newStepMission = Action.async { implicit request =>
    val data = FormTool.stepForm.bindFromRequest().get
    val userId = Tool.getUserId
    val row = StepMissionRow(0, s"${data.missionName}", userId, data.stepNum, data.id,
      new DateTime(), None, "preparing", data.kitName, containerName = data.containerName)
    println(row)
    stepMissionDao.insert(row).flatMap(_ => stepMissionDao.selectByMissionName(row.userId, row.missionName)).flatMap { mission =>
      val newMission = mission.copy(state = "wait")
      stepMissionDao.update(newMission).map { x =>
        Ok(Json.obj("valid" -> true))
      }
    }
  }

  def checkSampleExist = Action.async { implicit request =>
    val data = FormTool.missionIdForm.bindFromRequest().get
    val userId = Tool.getUserId
    stepMissionDao.selectByMissionId(data.missionId).flatMap { row =>
      val sampleId = row.sampleId
      sampleDao.selectOpById(userId, sampleId)
    }.map { x =>
      val valid = x match {
        case Some(value) => true
        case None => false
      }
      Ok(Json.obj("valid" -> valid))
    }
  }

  def getLogContent = Action.async {
    implicit request =>
      val userId = Tool.getUserId
      val data = FormTool.missionIdForm.bindFromRequest().get
      stepMissionDao.selectByMissionId(data.missionId).map {
        mission =>
          val missionIdDir = WebTool.getStepMissionIdDir(data.missionId)
          val logFile = new File(missionIdDir, s"log.txt")
          val logStr = FileUtils.readFileToString(logFile, "UTF-8")
          Ok(Json.toJson(logStr))
      }
  }

  def getMissionResultData = Action.async { implicit request =>
    val userId = Tool.getUserId
    val data = FormTool.missionIdForm.bindFromRequest().get
    stepMissionDao.select(data.missionId).flatMap { row =>
      val sampleIds = row.result.get
      sampleDao.selectAll(userId, sampleIds).map { x =>
        val dbSample = x.find(_.id == row.sampleId).get
        val totalArray = x.filter { y =>
          y.id != row.sampleId
        }.map { y =>
          Tool.getJsonBySampleWithStepNumOther(y, dbSample, row.kitName)
        }
        val totalColorSiteNames = totalArray.flatMap(_._2).toSet
        val head = Tool.getJsonBySampleWithStepNumSelf(dbSample, row.kitName, totalColorSiteNames)
        val array = head :: totalArray.map(_._1).toList
        val sortArray = array.sortBy { json =>
          (json \ "stepNum").as[Int]
        }
        val finalArray = sortArray.filter { json =>
          (json \ "id").as[String] == dbSample.id
        } ++ sortArray.filter { json =>
          (json \ "id").as[String] != dbSample.id
        }
        Ok(Json.toJson(finalArray))
      }
    }
  }

  def getMissionById = Action.async { implicit request =>
    val data = FormTool.missionIdForm.bindFromRequest().get
    stepMissionDao.selectByMissionId(data.missionId).map { x =>
      val json = Utils.getJsonByT(x)
      Ok(Json.toJson(json))
    }
  }

  def missionManageBefore = Action { implicit request =>
    Ok(views.html.user.stepMissionManage())
  }

  def missionResultBefore = Action { implicit request =>
    val data = FormTool.missionIdForm.bindFromRequest().get
    Ok(views.html.user.stepMissionResult(data.missionId))
  }

  def missionNameCheck = Action.async { implicit request =>
    val data = FormTool.missionNameForm.bindFromRequest.get
    val userId = Tool.getUserId
    stepMissionDao.selectOptionByMissionName(userId, data.missionName).map { mission =>
      mission match {
        case Some(y) => Ok(Json.obj("valid" -> false))
        case None =>
          Ok(Json.obj("valid" -> true))
      }
    }
  }

  def getAllMission = Action.async { implicit request =>
    val userId = Tool.getUserId
    stepMissionDao.selectAll(userId).map { x =>
      Future {
        val missionIds = x.map(_.id.toString)
        val missionDir = WebTool.getUserStepMissionDir
        missionDir.listFiles().filter {
          dir =>
            !missionIds.contains(dir.getName)
        }.foreach(Utils.deleteDirectory(_))
      }
      val array = Utils.getArrayByTs(x)
      Ok(Json.toJson(array))
    }
  }

  def deleteMissionById = Action.async {
    implicit request =>
      val userId = Tool.getUserId
      val data = FormTool.missionIdForm.bindFromRequest().get
      stepMissionDao.selectByMissionId(data.missionId).flatMap { mission =>
        Future {
          val missionIdDir = Tool.getStepMissionIdDir(mission)
          Utils.deleteDirectory(missionIdDir)
        }
        stepMissionDao.deleteById(userId, data.missionId).map { x =>
          Ok(Json.toJson("success"))
        }
      }
  }

  def exportAllDat = Action.async { implicit request =>
    val kitName = FormTool.kitNameForm.bindFromRequest().get.kitName
    val missionId = FormTool.missionIdForm.bindFromRequest().get.missionId
    val exportDatData = FormTool.exportDatForm.bindFromRequest().get
    val tmpDir = Tool.createTempDirectory("tmpDir")
    val outFile =new File(tmpDir, "out.file")
    val userId = Tool.getUserId
    stepMissionDao.select(missionId).flatMap { stepMission =>
      val sampleIds = stepMission.result.get
      sampleDao.selectAll(userId, sampleIds)
    }.map { samples =>
      Tool.samples2Dat(tmpDir,samples.toList,kitName,exportDatData)
      Ok.sendFile(outFile, onClose = () => {
        Tool.deleteDirectory(tmpDir)
      })
    }
  }

  def updateMissionSocket = WebSocket.accept[JsValue, JsValue] { implicit request =>
    val userId = Tool.getUserId
    case class MissionAction(beforeMissions: Seq[StepMissionRow], action: String)
    ActorFlow.actorRef(out => Props(new Actor {
      override def receive: Receive = {
        case msg: JsValue if (msg \ "info").as[String] == "start" =>
          val beforeMissions = Utils.execFuture(stepMissionDao.selectAll(userId))
          out ! Utils.getJsonByTs(beforeMissions)
          system.scheduler.scheduleOnce(3 seconds, self, MissionAction(beforeMissions, "update"))
        case MissionAction(beforeMissions, action) =>
          stepMissionDao.selectAll(userId).map {
            missions =>
              val currentMissions = missions
              if (currentMissions.size != beforeMissions.size) {
                out ! Utils.getJsonByTs(currentMissions)
              } else {
                val b = currentMissions.zip(beforeMissions).forall {
                  case (currentMission, beforeMission) =>
                    currentMission.id == beforeMission.id && currentMission.state == beforeMission.state
                }
                if (!b) {
                  out ! Utils.getJsonByTs(currentMissions)
                }
              }
              system.scheduler.scheduleOnce(3 seconds, self, MissionAction(currentMissions, "update"))
          }
        case _ =>
          self ! PoisonPill
      }

      override def postStop(): Unit = {
        self ! PoisonPill
      }
    }))
  }


}
